Conversation
…61) Real root cause of push-notification failures against Workstacean. Visibility fix in #62 surfaced the actual error — Workstacean's callback endpoint was returning HTTP 401 "Invalid notification token", not the 404/silence we initially feared. The A2A spec's PushNotificationConfig allows two interchangeable shapes for the webhook bearer token: 1. Top-level `token` — the simple form @a2a-js/sdk serialises by default. Workstacean's SkillDispatcherPlugin uses this path: `setTaskPushNotificationConfig({ pushNotificationConfig: {url, token} })` (a2a-executor.ts:329-331). 2. Structured `authentication.credentials` — RFC-8821 AuthenticationInfo with `schemes` + `credentials`. Quinn only read shape #2. Workstacean's top-level token fell on the floor → stored `cfg.token = None` → `_deliver_webhook` skipped the `Authorization: Bearer <token>` header → Workstacean's callback handler found no bearer, computed `providedToken = ""`, mismatched the expected token, and rejected with 401 for every delivery. Live evidence from v0.1.8 logs: push config registered (jsonrpc) task=1167ad7f... → .../callback/1167ad7f... webhook delivered task=1167ad7f... state=completed → .../callback/1167ad7f... (401) Fix: factored token extraction into `_extract_push_token(cfg)` that checks top-level first, falls back to `authentication.credentials`, and returns None cleanly when neither is present. All three PushNotificationConfig parse sites (JSON-RPC set handler, REST alias handler, shared `_parse_push_config` at submit time) now route through it — no more divergent token parsing across the three entry points. 4 regression tests cover: top-level token, structured authentication, both-present-precedence, none-present-is-None. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
WalkthroughThis PR introduces a helper function Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@a2a_handler.py`:
- Around line 653-658: The code assumes cfg.get("authentication") returns a dict
and calls auth.get(...), which throws AttributeError for truthy non-dict values;
change the auth handling to validate its type before using .get (e.g., retrieve
auth = cfg.get("authentication"); if not isinstance(auth, dict): auth = {}),
then read creds = auth.get("credentials") and return creds only if it's a
non-empty string (as with the existing creds check). This targets the auth
variable and creds extraction in the shown function to avoid 500s on malformed
config.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 6efdb91d-dc3e-4891-922f-bcdb3e287384
📒 Files selected for processing (2)
a2a_handler.pytests/test_a2a_handler.py
| top_level = cfg.get("token") | ||
| if isinstance(top_level, str) and top_level: | ||
| return top_level | ||
| auth = cfg.get("authentication") or {} | ||
| creds = auth.get("credentials") | ||
| return creds if isinstance(creds, str) and creds else None |
There was a problem hiding this comment.
Handle non-dict authentication to avoid 500s on malformed config.
Line 656 assumes authentication is a dict. If a client sends a truthy non-object value, auth.get(...) raises AttributeError and the request fails with 500 instead of safely resolving to None.
🔧 Proposed fix
def _extract_push_token(cfg: dict) -> str | None:
@@
- auth = cfg.get("authentication") or {}
- creds = auth.get("credentials")
+ auth = cfg.get("authentication")
+ if not isinstance(auth, dict):
+ return None
+ creds = auth.get("credentials")
return creds if isinstance(creds, str) and creds else None🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@a2a_handler.py` around lines 653 - 658, The code assumes
cfg.get("authentication") returns a dict and calls auth.get(...), which throws
AttributeError for truthy non-dict values; change the auth handling to validate
its type before using .get (e.g., retrieve auth = cfg.get("authentication"); if
not isinstance(auth, dict): auth = {}), then read creds =
auth.get("credentials") and return creds only if it's a non-empty string (as
with the existing creds check). This targets the auth variable and creds
extraction in the shown function to avoid 500s on malformed config.
Follow-up to #64 / #62. Two operator-visible tweaks in the streaming + push notifications section: - Call out that the token-parsing accepts both spec shapes (top-level `token` used by @a2a-js/sdk + Workstacean, and structured `authentication.credentials` for RFC-8821-style callers). - Document `LOG_LEVEL=INFO` (the default) as the switch that surfaces every push-config registration + webhook delivery attempt. Paired with workstacean docs that call out these as gold-standard requirements for every a2a agent.
Follow-up to #64 / #62. Two operator-visible tweaks in the streaming + push notifications section: - Call out that the token-parsing accepts both spec shapes (top-level `token` used by @a2a-js/sdk + Workstacean, and structured `authentication.credentials` for RFC-8821-style callers). - Document `LOG_LEVEL=INFO` (the default) as the switch that surfaces every push-config registration + webhook delivery attempt. Paired with workstacean docs that call out these as gold-standard requirements for every a2a agent. Co-authored-by: GitHub CI <ci@example.com>
Actual root cause of Quinn #61
Visibility fix in #62 surfaced the real error: Workstacean's callback endpoint was returning HTTP 401 "Invalid notification token" on every Quinn webhook delivery. Not 404, not silence — 401.
Why
The A2A spec `PushNotificationConfig` allows two interchangeable shapes for the bearer token:
Quinn only read shape #2. Workstacean's top-level token fell on the floor → `cfg.token = None` → `_deliver_webhook` skipped the `Authorization: Bearer ` header → Workstacean's callback handler (`a2a-callback.ts:47-50`) found no bearer, computed `providedToken = ""`, mismatched, returned 401.
Live evidence (post-#62 logs)
```
push config registered (jsonrpc) task=1167ad7f... → .../callback/1167ad7f...
webhook delivered task=1167ad7f... state=completed → .../callback/1167ad7f... (401)
```
Fix
Factored token extraction into `_extract_push_token(cfg)` — checks top-level first, falls back to `authentication.credentials`, returns None when neither is present. All three PushNotificationConfig parse sites (JSON-RPC set handler, REST alias, shared `_parse_push_config` at submit time) now route through it. No more divergent parsing across entry points.
Closes
Closes #61
Test plan
🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Tests